#include <QtWidgets>
#include <QPushButton>
#include <QMessageBox>
#include <string>
#include "glwidget.h"
#include "MainWindow.h"
#include "imgproc/imgproc.hpp"
#include "opencv2/opencv.hpp"
using namespace cv;


MainWindow::MainWindow(QWidget *parent)
    : QWidget(parent)
{
    //create log file
    logFile = new QFile("log.txt");
    if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text))
    {
        logFile = NULL;
        logTextStream = NULL;
    }
    else
    {
        logTextStream = new QTextStream(logFile);
        *logTextStream<<"Log file creat\n";
        logTextStream->flush();
    }

    //intial members
    currentImageIndex = 0;
    bufferFileName = "buffer.pfm";
    poseEst = new PoseEstimation();
    poseEst->SetLogTextStream(logTextStream);

    pFaceGeoEst = new FaceGeoEstimation();
    pFaceGeoEst->SetLogTextStream(logTextStream);

    //create ui
    QGridLayout *mainLayout = new QGridLayout;

    QColor clearColor;
    clearColor.setRgb(0,0,0);
    glWidget = new GLWidget(0,0);
    glWidget->setClearColor(clearColor);
    glWidget->SetLogTextStreamObj(logTextStream);
 //  glWidget->rotateBy(+42 * 16, +42 * 16, -21 * 16);
    mainLayout->addWidget(glWidget, 0,0);
    connect(glWidget, SIGNAL(clicked()),
            this, SLOT(setCurrentGlWidget()));


    imageQWidget = new QLabel;
    imageQWidget->setBackgroundRole(QPalette::Base);
    imageQWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    imageQWidget->setScaledContents(true);


    QString fileName("face.jpg");
    QImage image(fileName);
    if (image.isNull()) {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Cannot load %1.").arg(QDir::toNativeSeparators(fileName)));

        imageQWidget->setPixmap(QPixmap());
        imageQWidget->adjustSize();
    }

    imageQWidget->setPixmap(QPixmap::fromImage(image));
 //   printAct->setEnabled(true);
 //   fitToWindowAct->setEnabled(true);
 //   updateActions();

 //   if (!fitToWindowAct->isChecked())
 //       imageQWidget->adjustSize();
    mainLayout->addWidget(imageQWidget, 0,1);




    QGridLayout *subLayout1 = new QGridLayout;
    mainLayout->addLayout(subLayout1, 1, 0);

    QPushButton *prtFace = new QPushButton(tr("PrtFace"));
    prtFace->setFont(QFont("Times", 8, QFont::Bold));
    subLayout1->addWidget(prtFace,0,0);
    connect(prtFace, SIGNAL(clicked()), this, SLOT(renderToPBuffer()));

    QPushButton *readFiducials = new QPushButton(tr("ComputePose"));
    readFiducials->setFont(QFont("Times", 8, QFont::Bold));
    subLayout1->addWidget(readFiducials,0,1);
    connect(readFiducials, SIGNAL(clicked()), this, SLOT(readFiducials()));

    QPushButton *updateDepth = new QPushButton(tr("UpdateDepth"));
    updateDepth->setFont(QFont("Times", 8, QFont::Bold));
    subLayout1->addWidget(updateDepth,1,1);
    connect(updateDepth, SIGNAL(clicked()), this, SLOT(updateDepth()));

    QPushButton *depthEst = new QPushButton(tr("DepthEst"));
    depthEst->setFont(QFont("Times", 8, QFont::Bold));
    subLayout1->addWidget(depthEst,1,0);
    connect(depthEst, SIGNAL(clicked()), this, SLOT(depthEstimation()));



    QGridLayout *subLayout2 = new QGridLayout;
    mainLayout->addLayout(subLayout2, 1, 1);

    QPushButton *showPose = new QPushButton(tr("ShowPose"));
    showPose->setFont(QFont("Times", 8, QFont::Bold));
    subLayout2->addWidget(showPose, 0, 0);
    connect(showPose, SIGNAL(clicked()), this, SLOT(showPose()));

    QPushButton *test = new QPushButton(tr("Test"));
    test->setFont(QFont("Times", 8, QFont::Bold));
    subLayout2->addWidget(test, 0, 1);
    connect(test, SIGNAL(clicked()), this, SLOT(test()));



    setLayout(mainLayout);

    *logTextStream<<"create widgets\n";
    logTextStream->flush();

    currentGlWidget = glWidget;

    QTimer *timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(rotateOneStep()));
    timer->start(20);

    setWindowTitle(tr("FaceModeling"));
    *logTextStream<<"set window title\n";
    logTextStream->flush();


}

MainWindow::~MainWindow()
{
    if(logTextStream)
        delete logTextStream;
    if(logFile)
    {
        logFile->close();
        delete logFile;
    }
    delete poseEst;
    delete pFaceGeoEst;

}

//QSize MainWindow::sizeHint() const
//{
//    return QSize(1024, 1024);
//}



void MainWindow::setCurrentGlWidget()
{
    currentGlWidget = qobject_cast<GLWidget *>(sender());
}

void MainWindow::rotateOneStep()
{
//    if (currentGlWidget)
//        ((GLWidget*)currentGlWidget)->rotateBy(+2 * 16, +2 * 16, -1 * 16);
}

void MainWindow::readFiducials()
{
    if(!poseEst)
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),"No PoseEstimation Object!");
        return;
    }
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open Fiducial File"), "", tr("Fiducial Files (*.txt)"));
    if(fileName=="" || !poseEst->ReadImageFiducials(fileName))
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Read %1 failed. ").arg("Fiducials files"));
        return;

    }
/*    if(!poseEst->ReadMeshFiducials("meshFiducials.txt", "face.pfm"))
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Read %1 failed. ").arg("meshFiducials.txt or face.pfm"));
        return;

    }


    QTime t;
    t.start();
    QString str;

    poseEst->ComputePosePara();

    //debug begin
  str = QString("ComputePosePara %1\n").arg(t.elapsed());
  if(logTextStream)
  {
       *logTextStream<<str;
      logTextStream->flush();
  }
  t.restart();
  //debug end

    QVector<short> list = poseEst->GetFrontalImgListInUsedImgList();
//    for(int i=0; i<list.size(); i++)
//    {
//        poseEst->MappingImagetoGeo(list[i], "face.pfm");
//    }
    poseEst->ConstructPixelMatrix(list, "face.pfm");

    //debug begin
  str = QString("MappingImagetoGeo %1\n").arg(t.elapsed());
  if(logTextStream)
  {
       *logTextStream<<str;
      logTextStream->flush();
  }
  t.restart();
  //debug end


*/
    QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                             tr("Done!"));
}

void MainWindow::showPose()
{
    if(poseEst->GetUsedImgNum()==0)
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Push the ComputePose button first. "));
        return;
    }
    if(currentImageIndex == poseEst->GetUsedImgNum())
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("The last image %1 is Done. ").arg(currentImageIndex));
        currentImageIndex=0;
        return;
    }
    QVector<short> list = poseEst->GetFrontalImgListInUsedImgList();
    if(currentImageIndex==list.size())
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("The last frontal image %1 is Done. ").arg(currentImageIndex));
        currentImageIndex=0;
        return;
    }

    QString fileName = poseEst->UpdateMeshColor(glWidget->GetCurrentMesh(), list[currentImageIndex]);
//    bufferFileName = fileName + ".pfm";
//    glWidget->renderToPBuffer(bufferFileName);


    currentImageIndex++;


}

void MainWindow::updateDepth()
{   

    Mesh* pCurrentMesh = glWidget->GetCurrentMesh();
    if(!pCurrentMesh)
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("No mesh to update!"));
        return;
    }

    QString fileName = QFileDialog::getOpenFileName(this, tr("Open Depth File"), "", tr("Depth Files (*.pfm)"));
    if(fileName=="")
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Read %1 failed. ").arg("Depth files"));
        return;

    }

//    pCurrentMesh->RemoveOccludedVertices(glWidget->GetModelViewMatrix(), glWidget->GetProjMatrix(), "viewdepth.pfm");
    pCurrentMesh->UpdateVerticesDepth(glWidget->GetModelViewMatrix(), glWidget->GetProjMatrix(), fileName);



    QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                             tr("Depth update Done!"));

}


void MainWindow::depthEstimation()
{

    if(!poseEst->GetUsedImgNum())
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Push the ComputePose button first. "));
        return;
    }



    QTime t;
    t.start();
    QString str;

    pFaceGeoEst->ReadTemplate("template.pfm");
    PFMImage templateNormalImg;
    templateNormalImg.ReadFromFile("templateNormal.pfm");
    PFMImage albedoImg;
    albedoImg.ReadFromFile("templateAlbedo.pfm");
    cv::Mat templateNormalMatrix(4, pFaceGeoEst->GetPixelNum(), CV_32F);
    int i=0;
    short w = templateNormalImg.GetWidth();
    short h = templateNormalImg.GetHeight();

    for(short u=0; u<w; u++)
    {
        for(short v=0; v<h; v++)
        {
            QVector3D normal = templateNormalImg.GetPixelValue(u,v);
            QVector3D color = albedoImg.GetPixelValue(u,v);
            if((color.x()+color.y()+color.z())>0.0f)
            {
                templateNormalMatrix.at<float>(0,i) = color.x()*0.299 + color.y()*0.587 + color.z()*0.114;
                templateNormalMatrix.at<float>(1,i) = normal.x();
                templateNormalMatrix.at<float>(2,i) = normal.y();
                templateNormalMatrix.at<float>(3,i) = normal.z();
                i++;
            }
        }
    }
    pFaceGeoEst->SetShapeMatrixTemplate(templateNormalMatrix);
    pFaceGeoEst->ReadReferenceGeoImage("templateFace.pfm");

    //debug begin
  str = QString("Init FaceGeoEst %1\n").arg(t.elapsed());
  if(logTextStream)
  {
       *logTextStream<<str;
      logTextStream->flush();
  }
  //debug end


  for(int i=0; i<4; i++)
  {
      if(logTextStream)
      {
          str = QString("Iteration %1 ****************************************************\n").arg(i+1);
          *logTextStream<<str;
          logTextStream->flush();
      }

      t.restart();
      QString faceFileName = QString("face%1.pfm").arg(i);
      poseEst->ReadMeshFiducials("meshFiducials.txt", faceFileName);
      poseEst->ComputePosePara();
      //debug begin
      str = QString("ComputePosePara %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end

      QVector<short> list = poseEst->GetFrontalImgListInUsedImgList();
      /*int usedImgNum = poseEst->GetUsedImgNum();
      QVector<short> list;
      list.resize(usedImgNum);
      for(int i=0; i<usedImgNum; i++)
      {
          list[i] = i;
      }*/
      poseEst->ConstructPixelMatrix(list, faceFileName);
      //debug begin
//      for(int j=0; j<6; j++)
//      {
//          poseEst->MappingImagetoGeo(list[j], faceFileName);
//      }

      str = QString("MappingImagetoGeo %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end


      //pFaceGeoEst->ConstructPixelMatrix(poseEst->GetImagesPath(), poseEst->GetFrontImgFileNameList(), poseEst->GetFrontalImgIndexList());
      pFaceGeoEst->SetPixelMatrix(poseEst->GetPixelMatrix());
      //debug begin
      str = QString("ConstructPixelMatrix %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end


      pFaceGeoEst->InitialLightShapeEstimation(4);

      //debug begin
      str = QString("InitialLightShapeEstimation %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end


      //    pFaceGeoEst->OutputReconstructFaceSVD(poseEst->GetImagesPath(), poseEst->GetFrontImgFileNameList());
      pFaceGeoEst->FaceNormalEstimation(0.1);

      //debug begin
      str = QString("FaceNormalEstimation %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end




      pFaceGeoEst->ComputeAmbiguityMatrix();
      cv::Mat sm = pFaceGeoEst->GetShapeMatrix();
      str=QString("normalAfterAmbiguity%1.pfm").arg(i+1);
      pFaceGeoEst->OutputNormalMap(sm, str);

      //debug begin
      str = QString("ComputeAmbiguityMatrix %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end



      cv::Mat depth;
      if(i==0)
      {

          depth = pFaceGeoEst->DepthFromNormalByIntegration(pFaceGeoEst->GetShapematrixTemplate());

          //debug begin
          str = QString("DepthFromNormalByIntegration %1\n").arg(t.elapsed());
          if(logTextStream)
          {
              *logTextStream<<str;
              logTextStream->flush();
          }
          t.restart();
          //debug end

          QVector3D co;
          co = pFaceGeoEst->ComputeGBRCofficient(depth);

          //debug begin
          str = QString("ComputeGBRCofficient %1\n").arg(t.elapsed());
          if(logTextStream)
          {
              *logTextStream<<str;
              logTextStream->flush();
          }
          t.restart();
          //debug end
      }

      /*QVector3D coTmp;
      coTmp.setX(-0.01755519);
      coTmp.setY(-0.01692105);
      coTmp.setZ(-0.01057527);
      pFaceGeoEst->SetGBRCofficient(coTmp);*/

      depth = pFaceGeoEst->DepthFromNormalByIntegration(pFaceGeoEst->GetShapeMatrix());

      //debug begin
      str = QString("DepthFromNormalByIntegration %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end



      pFaceGeoEst->ReconstructDepthMapByIntegration();
      str=QString("face%1.pfm").arg(i+1);
      pFaceGeoEst->OutputReconGeo(str);


      //debug begin
      str = QString("ReconstructDepthMapByIntegration %1\n").arg(t.elapsed());
      if(logTextStream)
      {
          *logTextStream<<str;
          logTextStream->flush();
      }
      t.restart();
      //debug end

      poseEst->IterNumAdd();
      pFaceGeoEst->IterNumAdd();
  }





    QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                             tr("Depth Estimation Done!"));
}


void MainWindow::test()
{

    Mesh* pCurrentMesh = glWidget->GetCurrentMesh();
   pCurrentMesh->RemoveOccludedVertices(glWidget->GetModelViewMatrix(), glWidget->GetProjMatrix(), "viewdepth.pfm");



   /*  i=0;
    for(short u=0; u<w; u++)
    {
        for(short v=0; v<h; v++)
        {
            QVector3D normal;
            QVector3D color = albedoImg.GetPixelValue(u,v);
            if((color.x()+color.y()+color.z())>0.0f)
            {
                normal.setX(sm.at<float>(1,i));
                normal.setY(sm.at<float>(2,i));
                normal.setZ(sm.at<float>(3,i));
                normal.normalize();
                templateNormalImg.SetPixelValue(u,v,normal);
                i++;
            }
        }
    }
    templateNormalImg.WriteToFile("reconsNormal.pfm");*/




/*
 * test opencv
 *
 *   cv::Mat src, dst;
    cv::Mat dst1(480, 640, CV_8UC4);
    for (int i = 0; i < dst1.rows; ++i) {
            for (int j = 0; j < dst1.cols; ++j) {
                Vec4b& rgba = dst1.at<Vec4b>(i, j);
                rgba[0] = UCHAR_MAX;
                rgba[1] = saturate_cast<uchar>((float (dst1.cols - j)) / ((float)dst1.cols) * UCHAR_MAX);
                rgba[2] = saturate_cast<uchar>((float (dst1.rows - i)) / ((float)dst1.rows) * UCHAR_MAX);
                rgba[3] = saturate_cast<uchar>(0.5 * (rgba[1] + rgba[2]));
            }
        }
    src = dst1.clone();

    QString fileName("test.png");
    std::string teststr = fileName.toStdString();
    std::string str("1.png");
    imwrite(fileName.toStdString(), dst1);

    std::string str2("face.jpg");
    src = imread(str2);
    if(!src.data)
    {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Read %1 failed. ").arg("face.jpg"));
        return;
    }
    dst = src.clone();
    imwrite("facetext.jpg", dst);
*/


//    QMessageBox::information(this, QGuiApplication::applicationDisplayName(),tr("This is %1.").arg("test"));

}

void MainWindow::renderToPBuffer()
{
    glWidget->renderToPBuffer(bufferFileName);
    QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                             tr("Save %1 Sucessful. ").arg(bufferFileName));

}
